/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.cadmiumjdbc;

import java.util.Calendar;
import java.util.Date;

import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;
import fr.x9c.cadmium.primitives.cadmium.Cadmium;

/**
 * This class provides the primitives related to the <tt>java.util.Date</tt> class.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.1
 * @since 1.1
 */
@PrimitiveProvider
public final class DateBase {

    /**
     * No instance of this class.
     */
    private DateBase() {
    } // end empty constructor

    /**
     * Compares two dates.
     * @param ctxt context
     * @param datebase first date
     * @param datebase2 second date
     * @return <tt>true</tt> if the first date is strictly after the second one,
     *         <tt>false</tt> otherwise
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_after(final CodeRunner ctxt,
                                                   final Value datebase,
                                                   final Value datebase2)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            final Date db2 = (Date) datebase2.asBlock().asCustom();
            return db.after(db2) ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_after(CodeRunner, Value, Value)'

    /**
     * Compares two dates.
     * @param datebase first date
     * @param datebase2 second date
     * @return <tt>true</tt> if the first date is strictly before the second one,
     *         <tt>false</tt> otherwise
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_before(final CodeRunner ctxt,
                                                    final Value datebase,
                                                    final Value datebase2)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            final Date db2 = (Date) datebase2.asBlock().asCustom();
            return db.before(db2) ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_before(CodeRunner, Value, Value)'

    /**
     * Compares two dates.
     * @param datebase first date
     * @param datebase2 second date
     * @return zero if passed dates are equal, a negative value if the first
     *         date is before the second one, a positive value otherwise
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_compare_to(final CodeRunner ctxt,
                                                        final Value datebase,
                                                        final Value datebase2)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            final Date db2 = (Date) datebase2.asBlock().asCustom();
            return Value.createFromLong(db.compareTo(db2));
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_compare_to(CodeRunner, Value, Value)'

    /**
     * Compares two dates.
     * @param datebase first date
     * @param datebase2 second date
     * @return <tt>true</tt> if the dates are equal,
     *         <tt>false</tt> otherwise
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_equals(final CodeRunner ctxt,
                                                    final Value datebase,
                                                    final Value datebase2)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            final Date db2 = (Date) datebase2.asBlock().asCustom();
            return db.equals(db2) ? Value.TRUE : Value.FALSE;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_equals(CodeRunner, Value, Value)'

    /**
     * Returns the day of the month of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the day of the month of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_day_of_month(final CodeRunner ctxt,
                                                              final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.DAY_OF_MONTH);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_day_of_month(CodeRunner, Value)'

    /**
     * Returns the day of the week of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the day of the week of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_day_of_week(final CodeRunner ctxt,
                                                             final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.DAY_OF_WEEK);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_day_of_week(CodeRunner, Value)'

    /**
     * Returns the day of the week (in the current month) of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the day of the week (in the current month) of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_day_of_week_in_month(final CodeRunner ctxt,
                                                                      final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.DAY_OF_WEEK_IN_MONTH);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_day_of_week_in_month(CodeRunner, Value)'

    /**
     * Returns the day of the year of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the day of the year of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_day_of_year(final CodeRunner ctxt,
                                                             final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.DAY_OF_YEAR);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_day_of_year(CodeRunner, Value)'

    /**
     * Returns the hour of the day of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the hour of the day of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_hour_of_day(final CodeRunner ctxt,
                                                             final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.HOUR_OF_DAY);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_hour_of_day(CodeRunner, Value)'

    /**
     * Returns the millisecond of the second of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the millisecond of the second of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_millisecond(final CodeRunner ctxt,
                                                             final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.MILLISECOND);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_millisecond(CodeRunner, Value)'

    /**
     * Returns the minute of the hour of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the minute of the hour of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_minute(final CodeRunner ctxt,
                                                        final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.MINUTE);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_minute(CodeRunner, Value)'

    /**
     * Returns the month of the year of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the month of the year of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_month(final CodeRunner ctxt,
                                                       final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.MONTH);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_month(CodeRunner, Value)'

    /**
     * Returns the second of the minute of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the second of the minute of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_second(final CodeRunner ctxt,
                                                        final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.SECOND);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_second(CodeRunner, Value)'

    /**
     * Returns the week of the month of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the week of the month of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_week_of_month(final CodeRunner ctxt,
                                                               final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.WEEK_OF_MONTH);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_week_of_month(CodeRunner, Value)'

    /**
     * Returns the week of the year of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the week of the year of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_week_of_year(final CodeRunner ctxt,
                                                              final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.WEEK_OF_YEAR);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_week_of_year(CodeRunner, Value)'

    /**
     * Returns the year of the passed date.
     * @param ctxt context
     * @param datebase date
     * @return the year of the passed date
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_get_year(final CodeRunner ctxt,
                                                      final Value datebase)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            return getField(db, Calendar.YEAR);
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_get_year(CodeRunner, Value)'

    /**
     * Changes the day of the month of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_day_of_month(final CodeRunner ctxt,
                                                              final Value datebase,
                                                              final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.DAY_OF_MONTH, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_day_of_month(CodeRunner, Value, Value)'

    /**
     * Changes the day of the week of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_day_of_week(final CodeRunner ctxt,
                                                             final Value datebase,
                                                             final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.DAY_OF_WEEK, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_day_of_week(CodeRunner, Value, Value)'

    /**
     * Changes the day of the week (in the current month) of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_day_of_week_in_month(final CodeRunner ctxt,
                                                                      final Value datebase,
                                                                      final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.DAY_OF_WEEK_IN_MONTH, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_day_of_week_in_month(CodeRunner, Value, Value)'

    /**
     * Changes the day of the year of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_day_of_year(final CodeRunner ctxt,
                                                             final Value datebase,
                                                             final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.DAY_OF_YEAR, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_day_of_year(CodeRunner, Value, Value)'

    /**
     * Changes the hour of the day of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_hour_of_day(final CodeRunner ctxt,
                                                             final Value datebase,
                                                             final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.HOUR_OF_DAY, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_hour_of_day(CodeRunner, Value, Value)'

    /**
     * Changes the millisecond of the second of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_millisecond(final CodeRunner ctxt,
                                                              final Value datebase,
                                                              final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.MILLISECOND, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_millisecond(CodeRunner, Value, Value)'

    /**
     * Changes the minute of the hour of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_minute(final CodeRunner ctxt,
                                                        final Value datebase,
                                                        final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.MINUTE, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_minute(CodeRunner, Value, Value)'

    /**
     * Changes the month of the year of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_month(final CodeRunner ctxt,
                                                        final Value datebase,
                                                        final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.MONTH, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_month(CodeRunner, Value, Value)'

    /**
     * Changes the second of the minute of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_second(final CodeRunner ctxt,
                                                        final Value datebase,
                                                        final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.SECOND, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_second(CodeRunner, Value, Value)'

    /**
     * Changes the week of the month of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_week_of_month(final CodeRunner ctxt,
                                                               final Value datebase,
                                                               final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.WEEK_OF_MONTH, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_week_of_month(CodeRunner, Value, Value)'

    /**
     * Changes the week of the year of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_week_of_year(final CodeRunner ctxt,
                                                              final Value datebase,
                                                              final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.WEEK_OF_YEAR, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_week_of_year(CodeRunner, Value, Value)'

    /**
     * Changes the year of the passed date.
     * @param ctxt context
     * @param datebase date
     * @param v new value
     * @return <i>unit</i>
     * @throws Fail.Exception if any error occurs
     */
    @Primitive
    public static Value cadmiumjdbc_datebase_set_year(final CodeRunner ctxt,
                                                      final Value datebase,
                                                      final Value v)
        throws Fail.Exception {
        try {
            final Date db = (Date) datebase.asBlock().asCustom();
            setField(db, Calendar.YEAR, v.asLong());
            return Value.UNIT;
        } catch (final Exception e) {
            Cadmium.fail(ctxt, e);
            return Value.UNIT; // never reached
        } // end try/catch
    } // end method 'cadmiumjdbc_datebase_set_year(CodeRunner, Value, Value)'

    /**
     * Returns the value of a field for a date.
     * @param d date to get field from - should not be <tt>null</tt>
     * @param f field to get
     * @return the value of field <i>f</i> for date <i>d</i>
     */
    private static Value getField(final Date d, final int f) {
        assert d != null : "null d";
        final Calendar c = Calendar.getInstance();
        c.setTime(d);
        return Value.createFromLong(c.get(f));
    } // end method 'getField(Date, int)'

    /**
     * Changes the value of a field for a date.
     * @param d date to set field to - should not be <tt>null</tt>
     * @param f field to set
     * @param v new value for field
     */
    private static void setField(final Date d, final int f, final int v) {
        assert d != null : "null d";
        final Calendar c = Calendar.getInstance();
        c.setTime(d);
        c.set(f, v);
        d.setTime(c.getTimeInMillis());
    } // end method 'setField(Date, int, int)'

} // end class 'DateBase'
